Skip to content

fix: replace deprecated golang.org/x/net/websocket with gorilla/websocket#45

Open
Copilot wants to merge 2 commits intotrunkfrom
copilot/sub-pr-43
Open

fix: replace deprecated golang.org/x/net/websocket with gorilla/websocket#45
Copilot wants to merge 2 commits intotrunkfrom
copilot/sub-pr-43

Conversation

Copy link
Copy Markdown

Copilot AI commented Apr 1, 2026

golang.org/x/net/websocket is effectively deprecated by the Go team. This replaces it with github.com/gorilla/websocket v1.5.3, a stable and actively maintained alternative.

Changes

  • wsNetConn adapter (remote/remote.go): wraps *gorilla.Conn as a net.Conn to preserve the existing byte-stream interface used throughout the package. Gorilla is message-oriented, so Read() buffers full messages and returns chunks; Write() is mutex-protected (gorilla enforces one concurrent writer).
type wsNetConn struct {
    conn    *gorilla.Conn
    mu      sync.Mutex // serialises concurrent writes
    readBuf []byte
}
  • ListenForConnections(): replaced websocket.Handler(...) with gorilla.Upgrader + http.HandlerFunc. CheckOrigin set to allow all origins (internal-only service).
  • processShutdown(): type assertion updated from *websocket.Conn*wsNetConn; close frame sent via writeClose() using gorilla.WriteControl.
  • go.mod: gorilla/websocket v1.5.3 added as direct dep; golang.org/x/net demoted to indirect (still pinned at the security-patched v0.38.0).
  • Tests (remote/wsnetconn_test.go): new test file covering Read (single message, chunked, multiple messages), Write, concurrent write safety, writeClose close-code verification, LocalAddr/RemoteAddr, SetReadDeadline timeout behaviour, and a compile-time net.Conn interface check.

Base automatically changed from dependabot/go_modules/golang.org/x/net-0.38.0 to trunk April 1, 2026 02:55
Copy link
Copy Markdown
Author

Copilot AI commented Apr 1, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • invalidWebHook
    • Triggering command: /tmp/go-build2084257456/b285/remote.test /tmp/go-build2084257456/b285/remote.test -test.testlogfile=/tmp/go-build2084257456/b285/testlog.txt -test.paniconexit0 -test.timeout=10m0s ints�� stamppb/timestamp.pb.go --global (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title [WIP] chore(deps): bump golang.org/x/net to 0.38.0 fix: replace deprecated golang.org/x/net/websocket with gorilla/websocket Apr 1, 2026
Copilot AI requested a review from sjinks April 1, 2026 03:02
@sjinks sjinks force-pushed the copilot/sub-pr-43 branch from f949791 to ed2073c Compare April 1, 2026 03:07
@sjinks
Copy link
Copy Markdown
Member

sjinks commented Apr 1, 2026

@copilot, are the changes covered by tests?

Copy link
Copy Markdown
Author

Copilot AI commented Apr 1, 2026

@copilot, are the changes covered by tests?

Added in commit e06889a (remote/wsnetconn_test.go). The new tests cover:

  • Read — single message, chunked reads (large message with small buffer), and multiple sequential messages
  • Write — data received intact by the peer
  • Concurrent Write — no panics or errors when multiple goroutines write simultaneously (exercises the mutex)
  • writeClose — client receives a *gorilla.CloseError with the expected status code (4001)
  • LocalAddr / RemoteAddr — non-nil addresses returned
  • SetReadDeadline — a Read on a silent connection times out rather than blocking
  • Compile-time check that *wsNetConn satisfies net.Conn

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • invalidWebHook
    • Triggering command: /tmp/go-build3844489582/b285/remote.test /tmp/go-build3844489582/b285/remote.test -test.testlogfile=/tmp/go-build3844489582/b285/testlog.txt -test.paniconexit0 -test.timeout=10m0s -goversion go1.24.13 -c=4 -nolocalimports -importcfg /tmp/go-build3844489582/b244/importcfg -pack /opt/hostedtoolcache/go/1.24.13/x64/src/go/doc/comment.go conf�� cgo --global ux_amd64/vet ame (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@sjinks sjinks marked this pull request as ready for review April 3, 2026 01:05
Copilot AI review requested due to automatic review settings April 3, 2026 01:05
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the deprecated golang.org/x/net/websocket usage in the remote package with github.com/gorilla/websocket, while preserving the existing net.Conn-based byte-stream interface via a small adapter.

Changes:

  • Introduces wsNetConn to adapt *gorilla.Conn to net.Conn semantics (buffering full WS messages and chunking on Read, serializing Write).
  • Updates the WebSocket listener to use gorilla.Upgrader + http.HandlerFunc, and updates shutdown handling to send a close control frame via the adapter.
  • Adds a new wsnetconn_test.go test suite and updates module dependencies for gorilla/websocket.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 6 comments.

File Description
remote/remote.go Replaces x/net/websocket server handling with gorilla/websocket, adds wsNetConn, updates shutdown close logic
remote/wsnetconn_test.go Adds tests for wsNetConn read/write behavior, deadlines, close frames, and concurrency
go.mod Adds gorilla/websocket as a direct dependency; demotes x/net to indirect
go.sum Adds checksums for gorilla/websocket

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread remote/wsnetconn_test.go
Comment on lines +29 to +48
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
t.Errorf("upgrade: %v", err)
return
}
serverConnCh <- ws
<-handlerDone // keep the handler alive until cleanup
}))

wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/"
clientWS, _, err := gorilla.DefaultDialer.Dial(wsURL, nil)
if err != nil {
close(handlerDone)
srv.Close()
t.Fatalf("dial: %v", err)
}

serverWS := <-serverConnCh

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newTestWSPair can deadlock indefinitely if the server-side upgrader.Upgrade fails (handler returns after t.Errorf without sending to serverConnCh, while the client goroutine blocks on <-serverConnCh). Consider propagating the upgrade error via a channel and/or using a select with timeout when waiting for serverWS so the test fails fast instead of hanging.

Copilot uses AI. Check for mistakes.
Comment thread remote/wsnetconn_test.go
Comment on lines +193 to +195
client.SetReadDeadline(time.Now().Add(50 * time.Millisecond))
client.ReadMessage() //nolint:errcheck // best-effort drain
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The drain goroutine busy-loops due to select { default: ... } and will repeatedly set deadlines and call ReadMessage as fast as possible, which can spike CPU and introduce test flakiness. Consider using a ticker/sleep, or block on ReadMessage with a deadline and handle timeout errors, while still honoring the stop channel.

Suggested change
client.SetReadDeadline(time.Now().Add(50 * time.Millisecond))
client.ReadMessage() //nolint:errcheck // best-effort drain
}
}
client.SetReadDeadline(time.Now().Add(50 * time.Millisecond))
if _, _, err := client.ReadMessage(); err != nil {
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
return
}

Copilot uses AI. Check for mistakes.
Comment thread remote/remote.go
Comment on lines +56 to +66
func (c *wsNetConn) Read(b []byte) (int, error) {
for len(c.readBuf) == 0 {
_, msg, err := c.conn.ReadMessage()
if err != nil {
return 0, err
}
c.readBuf = msg
}
n := copy(b, c.readBuf)
c.readBuf = c.readBuf[n:]
return n, nil
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wsNetConn.Read currently returns gorilla's close/EOF errors directly. Downstream code in this package checks for io.EOF specifically when the client disconnects; a *websocket.CloseError will be treated as an unexpected error and logged noisily. Consider translating normal close conditions (CloseError / unexpected EOF) to io.EOF (or net.ErrClosed) to preserve the previous net.Conn-like semantics.

Copilot uses AI. Check for mistakes.
Comment thread remote/remote.go
Comment on lines +42 to +67
// wsNetConn wraps a *gorilla.Conn and implements the net.Conn interface so that
// the WebSocket connection can be used wherever a plain net.Conn is expected.
// gorilla/websocket is message-oriented, so Read buffers an entire message and
// returns chunks of it on successive calls.
type wsNetConn struct {
conn *gorilla.Conn
mu sync.Mutex // serialises concurrent writes (gorilla requires one writer at a time)
readBuf []byte
}

func newWSNetConn(conn *gorilla.Conn) *wsNetConn {
return &wsNetConn{conn: conn}
}

func (c *wsNetConn) Read(b []byte) (int, error) {
for len(c.readBuf) == 0 {
_, msg, err := c.conn.ReadMessage()
if err != nil {
return 0, err
}
c.readBuf = msg
}
n := copy(b, c.readBuf)
c.readBuf = c.readBuf[n:]
return n, nil
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wsNetConn is presented as a net.Conn implementation, but Read is not concurrency-protected. gorilla/websocket explicitly allows only one concurrent reader; if two goroutines call Read concurrently (which net.Conn permits), this can race/panic. Consider adding a read-side mutex (or otherwise enforcing single-reader usage) to make the adapter safe for concurrent net.Conn use.

Copilot uses AI. Check for mistakes.
Comment thread remote/remote.go
Comment on lines +56 to +63
func (c *wsNetConn) Read(b []byte) (int, error) {
for len(c.readBuf) == 0 {
_, msg, err := c.conn.ReadMessage()
if err != nil {
return 0, err
}
c.readBuf = msg
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wsNetConn.Read uses ReadMessage, which buffers an entire WebSocket message into memory before returning any bytes. Without a read limit, a peer can send a very large frame/message and force large allocations (potential DoS). Consider setting a maximum message size (e.g., via conn.SetReadLimit) when creating/accepting the WebSocket connection.

Copilot uses AI. Check for mistakes.
Comment thread remote/remote.go
wsConn, ok := conn.(*wsNetConn)
if ok {
wsConn.WriteClose(shutdownErrorCode)
wsConn.writeClose(shutdownErrorCode)
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

processShutdown ignores the error returned by writeClose. If the close control frame fails to send (e.g., broken connection), it may be useful to log the error for diagnosis during shutdown.

Suggested change
wsConn.writeClose(shutdownErrorCode)
if err := wsConn.writeClose(shutdownErrorCode); err != nil {
log.Printf("remote: failed to send websocket close frame during shutdown. GUID: %s, err: %v\n", wpcli.GUID, err)
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants